/*
 * a small RMGUI library made for SDL2.
 * author: VisualGMQ
 *
 * Use `CreateXXX()` function to create widgets, and these widgets will be auto-released and can find them by `FindWidget`;
 * Otherwise you should delete it by yourself.
 */
_Pragma("once")

#include "pch.hpp"
#include "libmath.hpp"
#include "tool.hpp"
#include "engine.hpp"
#include "encoding.hpp"
#include "font.hpp"

namespace sdl_engine::gui {

constexpr int ID_ANY = -1;

void Init(Renderer& renderer,
          Font* defaultFont);
void Shutdown();
void EventHandle(const SDL_Event& e);

class Widget;

using ClickCb = std::function<void(Widget*)>;
using HoverCb = std::function<void(Widget*)>;
using OnFocuseCb = std::function<void(Widget*)>;
using LostFocuseCb = std::function<void(Widget*)>;

struct EventListener final {
    ClickCb clickCb_ = nullptr;
    OnFocuseCb onFocuseCb_ = nullptr;
    LostFocuseCb lostFocuseCb_ = nullptr;
};

enum Event {
    UNKNOWN = 0,
    MOUSEDOWN,
    MOUSEUP,
    MOUSEMOTION,
    MOUSEWHEEL,
    KEYDOWN,
    KEYUP,
    DRAG,
    GET_FOCUSE,
    LOST_FOCUSE,
    TEXT_INPUT,
    TEXT_EDITING,
};

// TODO use Box Model in Widget
class Widget {
public:
    Widget(int id);
    virtual ~Widget() = default;

    virtual void EventHandle(Event type);
    inline int GetID() const { return id_; }
    inline const Point& GetPosition() const { return rect_.position; }
    virtual void SetPosition(const Point& p);
    inline const Size& GetSize() const { return rect_.size; }
    virtual void SetSize(const Size& size);
    virtual Rect GetRect() const;
    inline void SetParent(Widget* parent) { parent_ = parent; }
    inline Widget* GetParent() const { return parent_; }

    void Render();

protected:
    EventListener eventListener_;

    virtual void renderLocal() {}
    virtual void renderGlobal() {}

private:
    int id_;
    Rect rect_;
    Widget* parent_ = nullptr;

    friend void EventHandle(const SDL_Event&);
};

class Containable: public Widget {
public:
    Containable(int id);
    virtual ~Containable() = default;
    void SetPosition(const Point&) override;
    void SetSize(const Size&) override;
    inline void SetSizeNoPack(const Size& s) {
        Widget::SetSize(s);
    }
    inline void SetPosNoPack(const Point& p) {
        Widget::SetPosition(p);
    }

    virtual void Pack() = 0;

protected:
};

using Layout = Containable;

Widget* GetFocuse();
void SetFocuse(Widget*);

using WidgetPtr = Widget*;

template <typename Layout>
void WidgetEventHandle(Layout& layout, Event type, WidgetPtr widget) {
    Rect widgetRect(widget->GetPosition(), widget->GetSize());
    Rect selfRect(layout.GetPosition(), layout.GetSize());
    if (IsRectsIntersect(selfRect, widgetRect) &&
        PointInRect(engine.GetMousePosition(), widgetRect)) {
        if (type == MOUSEDOWN) {
            auto focused = GetFocuse();
            if (focused != widget) {
                if (focused) {
                    focused->EventHandle(LOST_FOCUSE);
                }
                widget->EventHandle(GET_FOCUSE);
            }
            SetFocuse(widget);
        }
        widget->EventHandle(type);
    }
}

template <typename Layout>
void LayoutEventHandle(Layout& layout, Event type) {
    auto widgets = layout.GetWidgets();
    LinearForeach<WidgetPtr>(widgets,
                             std::bind(&WidgetEventHandle<Layout>,
                                       layout,
                                       type,
                                       std::placeholders::_1));
}

template <typename Layout>
void LayoutRender(Layout layout) {
    auto widgets = layout.GetWidgets();
    LinearForeach<WidgetPtr>(widgets, [](WidgetPtr& widget){
        widget->Render();
    });
}

/* a layout that arrange your widgets in a row or col. */
class LinearLayout: public Layout {
public:
    LinearLayout(int id, bool vertical = true)
        : Layout(id), vertical_(vertical) {}

    std::list<Widget*>& GetWidgets() { return widgets_; }

    inline void EventHandle(Event type) override {
        LayoutEventHandle(*this, type);
    }

    void AddWidget(Widget* widget);
    void Pack() override;

protected:
    void renderLocal() override;

private:
    bool vertical_;
    std::list<Widget*> widgets_;
};

/* @brief a layout that don't arrange widgets.You should set widgets position by yourself */
class GridLayout: public Layout {
public:
    struct WidgetInfo {
        Widget* widget;
        int row;
        int col;
        int rowspan;
        int colspan;
    };

    GridLayout(int id, const Size& gridSize)
        : Layout(id), gridSize_(gridSize) {}

    inline void EventHandle(Event type) override {
        LayoutEventHandle(*this, type);
    }

    std::vector<Widget*> GetWidgets();

    std::vector<WidgetInfo>& GetWidgetsInfo() { return widgets_; }

    /* @warning please make sure your widgets not overlap each other, or it will occur strange layout */
    void AddWidget(Widget* widget,
                   int row, int col,
                   int rowspan = 1, int colspan = 1);

    const Size& GetGridSize() const { return gridSize_; }
    void SetGridSize(const Size& size) { gridSize_ = size; }

    void Pack() override;

protected:
    void renderLocal() override;

private:
    Size gridSize_;

    std::vector<WidgetInfo> widgets_;

    void packOneWidget(Widget* widget,
                       int row, int col, int rowspan, int colspan);
};

class AbsoluteLayout: public Layout {
public:
    AbsoluteLayout(int id): Layout(id) {}

    void AddWidget(Widget* widget) {
        GetWidgets().push_back(widget);
    }

    std::list<Widget*>& GetWidgets() { return widgets_; }

    inline void EventHandle(Event type) override {
        LayoutEventHandle(*this, type);
    }

    void Pack() override;

protected:
    void renderLocal() override;

private:
    std::list<Widget*> widgets_;
};

class Slidebar;

class Window: public Containable {
public:
    Window(int id,
           const Point& pos,
           const Size& size,
           const std::string& title,
           bool showTitleBar = true);
    virtual ~Window() = default;

    void SetLayout(Layout* layout);
    inline Layout* GetLayout() { return layout_; }

    void SetTitle(const std::string& title);
    const std::string& GetTitle() const { return titleTextTexture_->GetText(); }
    inline void SetBorderColor(const Color& color) { borderColor_ = color; }
    inline const Color& GetBorderColor() const { return borderColor_; }
    inline void SetBgColor(const Color& bgColor_);
    inline const Color& GetBgColor() const { return bgColor_; }
    Size GetContentSize() const;
    Rect GetContentRect() const;

    void SetPosition(const Point&) override;
    void SetSize(const Size&) override;

    void EventHandle(Event) override;
    void Pack() override;

    void ShowTitleBar() { showTitleBar_ = true; }
    void HideTitleBar() { showTitleBar_ = false; }

protected:
    void renderLocal() override;

    void renderTitle();
    void renderContext();

private:
    Unique<TextTexture> titleTextTexture_ = nullptr;

    Color borderColor_ = Color::Black;
    Color bgColor_ = Color::White;

    Layout* layout_ = nullptr;

    Unique<Slidebar> scrollbar_ = nullptr;
    bool shouldShowSlidebar() const;

    bool showTitleBar_ = true;
};

class FoldWindow: public Window {
public:
    FoldWindow(int id,
               const Point& pos,
               const Size& size,
               const std::string& title);

    inline void Fold() { isFold_ = true; }
    inline void Expand() { isFold_ = false; }
    inline void ToggleFold() { isFold_ ? Expand() : Fold(); }
    inline bool IsFold() const { return isFold_; }
    Rect GetRect() const override;

    void EventHandle(Event) override;

protected:
    void renderLocal() override;

private:
    bool isFold_ = false;
    Rect getTitleBarRect() const;
};

class Button: public Widget {
public:
    Button(int id, const std::string& text, Font* font = nullptr);

    inline void SetFont(Font* font) { font_ = font; }
    void SetText(const std::string& text);
    inline const std::string& GetText() const { return textTexture_->GetText(); }

    inline void SetBgColor(const Color& color) { bgColor_ = color; }
    inline const Color& GetBgColor() const { return bgColor_; }
    inline void SetBorderColor(const Color& color) { borderColor_ = color; }
    inline const Color& GetBorderColor() const { return borderColor_; }
    inline void SetTextColor(const Color& color) { textColor_ = color; }
    inline const Color& GetTextColor() const { return textColor_; }

    void EventHandle(Event) override;

    inline void SetClickCb(ClickCb cb) {
        eventListener_.clickCb_ = cb;
    }

protected:
    Font* font_ = nullptr;
    Unique<TextTexture> textTexture_ = nullptr;

    void renderLocal() override;

private:
    Color bgColor_ = Color::White;
    Color borderColor_ = Color::Black;
    Color textColor_ = Color::Black;

    Color hoverBgColor_ = Color::Gray;
};

class ImageButton: public Widget {
public:
    ImageButton(int id, Texture* texture);

    void SetTexture(Texture*);

    void EventHandle(Event) override;

    inline void SetClickCb(ClickCb cb) {
        eventListener_.clickCb_ = cb;
    }

protected:
    void renderLocal() override;

private:
    Texture* texture_ = nullptr;
};

class Checkbox: public Button {
public:
    Checkbox(int id, const std::string& title, Font* font = nullptr);

    inline void SetBoxColor(const Color& color) { boxColor_ = color; }
    inline const Color& GetBoxColor() const { return boxColor_; }
    inline void Check() { checked_ = true; }
    inline void Uncheck() { checked_ = false; }
    inline bool IsChecked() { return checked_; }
    inline void ToggloeCheckState() { checked_ = !checked_; }

    virtual void EventHandle(Event type) override;

protected:
    void renderLocal() override;

private:
    int boxSize_;
    bool checked_ = false;
    Color boxColor_ = Color::Black;
    Color hoverBoxBgColor_ = Color::Gray;
    Color boxBgColor_ = Color::White;
    void renderBox(const Point& pos);
    void renderText(const Point& pos);
};

// FIXME: Label should inherit from button? or button should inherit from label?

class Label: public Widget {
public:
    Label(int id, const std::string& text, Font* font = nullptr);

    void SetText(const std::string& text);
    inline void SetTextColor(const Color& color) { textColor_ = color; }

    inline void SetClickCb(ClickCb cb) { eventListener_.clickCb_ = cb; }

    virtual void EventHandle(Event type) override;

protected:
    void renderLocal() override;

private:
    Unique<TextTexture> texture_ = nullptr;
    Font* font_ = nullptr;
    Color textColor_ = Color::Black;
};

class Slidebar: public Widget {
public:
    Slidebar(int id,
              int min, int max, int value,
              int lenPixel,
              bool verical);

    inline int GetMin() const { return min_; }
    inline int GetMax() const { return max_; }
    inline int GetValue() const { return value_; }
    inline int GetWidth() const { return width_; }
    inline int GetButtonLenPixel() const { return buttonLenPixel_; }
    inline void SetMin(int min) { min_ = min; value_ = std::max(min_, value_); }
    inline void SetMax(int max) {max_ = max; value_ = std::min(value_, max_); }
    inline void SetValue(int value) {
        value_ = std::min(std::max(value, min_), max_);
    }
    inline void SetButtonLenPixel(int len) { buttonLenPixel_ = len; }
    inline void SetWidth(int width) { width_ = width; }

    void EventHandle(Event) override;

protected:
    void renderLocal() override;

private:
    int min_;
    int max_;
    int value_;
    bool vertical_;
    int lenPixel_;
    int width_ = 10;
    int buttonLenPixel_ = 10;

    Color borderColor_ = Color::Black;
    Color bgColor_ = Color::White;
    Color buttonBgColor_ = Color::White;
    Color buttonHoverColor_ = Color::Gray;

    Rect getButtonRect() const;
};

class InputBox: public Widget {
public:
    InputBox(int id, int lenPixel);
    void EventHandle(Event) override;

protected:
    void renderLocal() override;

private:
    utf8string text_;

    Unique<Texture> textTexture_ = nullptr;

    Color borderColor_ = Color::Black;
    Color bgColor_ = Color::White;
    Color textColor_ = Color::Black;
    int cursor_ = 0;
    int lineX_ = 0;

    void tryGenTexture();
    void resetTextTexture();
    void resetCompTexture();
    int getUTF8FontWidth(Font* font, const utf8string::OneUTF8& c);
};

class TreeList: public Widget {
public:
    struct Node {
        friend class TreeList;

        Node(Font*, TreeList& treelist, const std::string&, Texture* = nullptr);

        bool expand = true;
        std::vector<Node> children;

        inline std::string GetText() const {
            return texture_.GetText();
        }

        inline Node& Append(const std::string& text, Texture* texture = nullptr) {
            children.emplace_back(&texture_.GetAttachedFont(), treelist_, text, texture);
            treelist_.Pack();
            return children.back();
        }

        inline void SetTexture(Texture* texture) { image_ = texture; }

    private:
        TextTexture texture_;
        Point relatePos_;
        TreeList& treelist_;
        Texture* image_ = nullptr;
    };

    TreeList(int id,
             const std::string& rootText,
             Texture* rootTexture = nullptr,
             Font* font = nullptr);

    inline Node& GetRoot() { return *root_; }

    void EventHandle(Event) override;

    inline Node* GetSelectNode() { return selectNode_; }

    void Pack();

    void Remove(Node& node);

    using ClickNodeCb = std::function<void(Node&)>;
    using FoldToggleCb = std::function<void(Node&)>;

    /* @brief callback(Node& node) */
    inline void SetClickNodeCb(ClickNodeCb cb) { clickNodeCb_ = cb; }
    /* @brief callback(Node& node) */
    inline void SetFoldToggleCb(FoldToggleCb cb) { foldToggleCb_ = cb; }

protected:
    constexpr static int FoldSignLen = 10;
    constexpr static int Indent = FoldSignLen + 10;

    void renderLocal() override;

    void renderHighlightArea(Node&);
    void renderNode(Node&);
    void renderFoldSign(Node&);
    void renderText(TextTexture&, const Point&);

private:
    ClickNodeCb clickNodeCb_ = nullptr;
    FoldToggleCb foldToggleCb_ = nullptr;

    Font* font_;
    Unique<Node> root_;

    Node* selectNode_ = nullptr;

    void packIter(Node&, int& y, int indentNum);
    Rect getFoldSignRect(const Node&);
    void eventHandleIter(Event, Node&);
    int calcNodeHeight(Node&);
};


/************************
 * some factory function
************************/

Window* CreateWindow(int id,
                     const Point& pos,
                     const Size& size,
                     const std::string& title,
                     bool showTitleBar = true);
FoldWindow* CreateFoldWindow(int id,
                             const Point& pos,
                             const Size& size,
                             const std::string& title);

LinearLayout* CreateLinearLayout(int id, bool vertical = true);
AbsoluteLayout* CreateAbsoluteLayout(int id);
GridLayout* CreateGridLayout(int id, const Size& gridSize);

Button* CreateButton(int id,
                     const std::string& text,
                     Font* font = nullptr);

Label* CreateLabel(int id,
                   const std::string& text,
                   Font* font = nullptr);

Checkbox* CreateCheckbox(int id,
                         const std::string& text,
                         Font* font = nullptr);

Slidebar* CreateSlidebar(int id,
                         int min, int max, int value,
                         int lenPixel = 10,
                         bool vertical = true);

InputBox* CreateInputBox(int id, int lenPixel);

TreeList* CreateTreeList(int id,
                         const std::string& rootText,
                         Texture* rootTexture = nullptr,
                         Font* font = nullptr);

ImageButton* CreateImageButton(int id, Texture*);

/***********************
 * some other function
***********************/

void FreeAllWidgets();

Widget* FindWidget(int id);

void Render();

// @brief for debug
void __PointOutFocuseWidget();

}
